package kvraft

import (
	`crypto/rand`
	`math/big`
	`sync`
	`sync/atomic`
	`time`

	`github.com/satori/go.uuid`

	`../labrpc`
)

type Clerk struct {
	mu             sync.Mutex
	servers        []*labrpc.ClientEnd
	lastLeader     int
	lastFindLeader time.Duration
	clientId       string
	requestId      uint64
}

func nrand() int64 {
	max := big.NewInt(int64(1) << 62)
	bigx, _ := rand.Int(rand.Reader, max)
	x := bigx.Int64()
	return x
}

func MakeClerk(servers []*labrpc.ClientEnd) *Clerk {
	ck := new(Clerk)
	ck.servers = servers
	ck.requestId = 1
	u, _ := uuid.NewV4()
	ck.clientId = u.String()
	// You'll have to add code here.
	go ck.waitFindLeader()
	return ck
}

func (ck *Clerk) addRequest() (string, uint64) {
	atomic.AddUint64(&ck.requestId, 1)
	return ck.clientId, ck.requestId
}

func (ck *Clerk) getAddClientGeneralArgs() ClientGeneralArgs {
	c, r := ck.addRequest()
	return ClientGeneralArgs{c, r}
}

func (ck *Clerk) getClientGeneralArgs() ClientGeneralArgs {
	return ClientGeneralArgs{ck.clientId, 0}
}

var getTimer time.Duration = 0
var getCount time.Duration = 0

func (ck *Clerk) Get(key string) string {
	getCount++
	c := time.Now()
	defer func() { getTimer += time.Since(c); TPrintf("getCount: %d, getTimerAvg: %s", getCount, (getTimer / getCount).String()); to.updateTimeOut() }()
	args := GetArgs{Key: key, ClientGeneralArgs: ck.getClientGeneralArgs()}
	reply := GetReply{}
	leader := ck.lastLeader
	for {
		DPrintf("[%s] Op: %d, Will Get. key: %s", ck.clientId, args.RequestId, key)
		reply = GetReply{}
		ok := ck.sendGet(leader, &args, &reply)
		if ok && reply.Err == OK {
			DPrintf("[%s] Op: %d, Get success", ck.clientId, args.RequestId)
			return reply.Value
		}
		if reply.Err == ErrWrongLeader {
			FPrintf("[%s] Op: %d, Get Wrong Leader, wait find Leader", ck.clientId, args.RequestId)
			leader = ck.waitFindLeader()
		} else {
			FPrintf("[%s] Op: %d, Get Wrong: %s", ck.clientId, args.RequestId, reply.Err)
			time.Sleep(500 * time.Millisecond)
		}
	}
}

var putTimer time.Duration = 0
var putCount time.Duration = 0

func (ck *Clerk) Put(key string, value string) {
	putCount++
	c := time.Now()
	defer func() { putTimer += time.Since(c); TPrintf("putCount: %d, putTimerAvg: %s", putCount, (putTimer / putCount).String()); to.updateTimeOut() }()
	//DPrintf("[%s] Put: key: %s, value: %s", ck.clientId, key, value)
	ck.PutAppend(key, value, "put")
}

var appendTimer time.Duration = 0
var appendCount time.Duration = 0

func (ck *Clerk) Append(key string, value string) {
	appendCount++
	c := time.Now()
	defer func() { appendTimer += time.Since(c); TPrintf("appendCount: %d, appendTimerAvg: %s", appendCount, (appendTimer / appendCount).String()); to.updateTimeOut() }()
	//DPrintf("[%s] Append: key: %s, value: %s", ck.clientId, key, value)
	ck.PutAppend(key, value, "append")
}

func (ck *Clerk) PutAppend(key string, value string, op string) {
	args := PutAppendArgs{Op: op, Key: key, Value: value, ClientGeneralArgs: ck.getAddClientGeneralArgs()}
	reply := PutAppendReply{}
	leader := ck.lastLeader
	for {
		DPrintf("[%s] Op: %d, Will PutAppend, key: %s, value: %s", ck.clientId, args.RequestId, key, value)
		reply = PutAppendReply{}
		ok := ck.sendPutAppend(leader, &args, &reply)
		if ok && (reply.Err == OK || reply.Err == ErrHasCommit) {
			DPrintf("[%s] Op: %d, PutAppend success", ck.clientId, args.RequestId)
			return
		}
		if reply.Err == ErrWrongLeader {
			FPrintf("[%s] Op: %d, PutAppend Wrong Leader, wait find Leader", ck.clientId, args.RequestId)
			leader = ck.waitFindLeader()
		} else {
			FPrintf("[%s] Op: %d, PutAppend Wrong: %s", ck.clientId, args.RequestId, reply.Err)
			time.Sleep(500 * time.Millisecond)
		}

	}
}

func (ck *Clerk) sendGet(leader int, args *GetArgs, reply *GetReply) bool {
	if leader < 0 {
		return false
	}
	FPrintf("[%s] will send Get", ck.clientId)
	c := make(chan bool)
	defer close(c)
	go func() {
		defer NeverPanic()
		ok := ck.servers[leader].Call("KVServer.Get", args, reply)
		c <- ok
	}()
	ticker := time.NewTicker(3 * time.Second)
	defer ticker.Stop()
	select {
	case ok := <-c:
		FPrintf("[%s] Get finish, network: %t", ck.clientId, ok)
		return ok
	case <-ticker.C:
		FPrintf("[%s] Get timeOut, return", ck.clientId)
		return false
	}
}

func (ck *Clerk) sendPutAppend(leader int, args *PutAppendArgs, reply *PutAppendReply) bool {
	if leader < 0 {
		return false
	}
	FPrintf("[%s] will send PutAppend", ck.clientId)
	c := make(chan bool)
	defer close(c)
	go func() {
		defer NeverPanic()
		ok := ck.servers[leader].Call("KVServer.PutAppend", args, reply)
		c <- ok
	}()
	ticker := time.NewTicker(3 * time.Second)
	defer ticker.Stop()
	select {
	case ok := <-c:
		FPrintf("[%s] PutAppend finish, network: %t", ck.clientId, ok)
		return ok
	case <-ticker.C:
		FPrintf("[%s] PutAppend timeOut, return", ck.clientId)
		return false
	}
}

func (ck *Clerk) waitFindLeader() int {
	ck.mu.Lock()
	defer ck.mu.Unlock()
	for {
		if leader := ck.lastLeader; Now()-ck.lastFindLeader < 500*time.Millisecond && leader != -1 {
			FPrintf("[%s] find leader: %d, return", ck.clientId, leader)
			return leader
		}
		ck.findLeader()
	}
}

func (ck *Clerk) findLeader() {
	ch := make(chan StateReply)
	for i := range ck.servers {
		go func(i int) {
			args, reply := StateArgs{}, StateReply{}
			ck.servers[i].Call("KVServer.State", &args, &reply)
			FPrintf("[%s] get state from %d", ck.clientId, i)
			reply.Index = i
			ch <- reply
		}(i)
	}

	ticker := time.NewTicker(3 * time.Second)
	defer ticker.Stop()
	leader, maxTerm := -1, 0
	for range ck.servers {
		select {
		case reply := <-ch:
			FPrintf("[%s] get state reply, index: %d, isLeader: %t, Term: %d", ck.clientId, reply.Index, reply.IsLeader, reply.Term)
			FPrintf("[%s] nowLeader: %d, Term: %d", ck.clientId, leader, maxTerm)
			if reply.Term >= maxTerm {
				maxTerm = reply.Term
				if reply.IsLeader {
					leader = reply.Index
				}
			}

		case <-ticker.C:
			break
		}
	}
	FPrintf("[%s] at Last, leader: %d", ck.clientId, leader)
	ck.lastLeader = leader
	ck.lastFindLeader = Now()
}
